"""
Search API Routes
Advanced search functionality for telemetry data
"""

from typing import List, Optional, Dict, Any, Tuple
from fastapi import APIRouter, Depends, HTTPException, status, Query
from pydantic import BaseModel, Field
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, and_, or_, text
import json

from app.core.database import get_business_db, get_extractor_db
from app.core.security import get_current_user
from app.models.business import Team, TeamFileAssignment
from app.models.extractor import Metric, MetricOwner, UkmMetric, Token
from app.api.v1.telemetry import get_team_assignment_for_file

router = APIRouter()


# Pydantic models
class SearchRequest(BaseModel):
    """Advanced search request model"""
    query: str = Field(..., min_length=1)
    filters: Optional[Dict[str, Any]] = None
    include_ukm: bool = True
    include_uma: bool = True
    include_enums: bool = True
    team_filter: Optional[str] = None
    component_filter: Optional[str] = None


class SearchResult(BaseModel):
    """Individual search result model"""
    id: int
    name: str
    type: str
    description: Optional[str]
    units: Optional[str]
    component: Optional[str]
    file_path: str
    line_number: Optional[int]
    owner: Optional[str]
    assigned_team_id: Optional[str] = None
    assigned_team_name: Optional[str] = None
    match_score: float = 0.0
    match_fields: List[str] = []

    class Config:
        from_attributes = True


class SearchResponse(BaseModel):
    """Search response model"""
    success: bool
    data: List[SearchResult]
    total: int
    query: str
    page: int
    page_size: int
    message: Optional[str] = None


class SearchSuggestionsResponse(BaseModel):
    """Search suggestions response"""
    success: bool
    suggestions: List[str]
    message: Optional[str] = None


def calculate_match_score(query: str, field_value: Optional[str]) -> Tuple[float, List[str]]:
    """Calculate match score and identify matching fields"""
    if not field_value:
        return 0.0, []

    query_lower = query.lower()
    field_lower = field_value.lower()

    score = 0.0
    match_fields = []

    # Exact match
    if query_lower == field_lower:
        return 1.0, ["exact_match"]

    # Starts with query
    if field_lower.startswith(query_lower):
        score += 0.8
        match_fields.append("starts_with")

    # Contains query
    if query_lower in field_lower:
        score += 0.6
        match_fields.append("contains")

    # Word boundaries
    words = query_lower.split()
    field_words = field_lower.split()

    matching_words = sum(1 for word in words if any(word in fw for fw in field_words))
    if matching_words > 0:
        score += (matching_words / len(words)) * 0.4
        match_fields.append("word_match")

    return min(score, 1.0), match_fields


@router.post("/", response_model=SearchResponse)
async def search_telemetry(
    search_request: SearchRequest,
    page: int = Query(1, ge=1),
    page_size: int = Query(50, ge=1, le=1000),
    current_user: dict = Depends(get_current_user),
    business_db: AsyncSession = Depends(get_business_db),
    extractor_db: AsyncSession = Depends(get_extractor_db)
):
    """Advanced search for telemetry points"""
    try:
        query = search_request.query
        filters = search_request.filters or {}

        # Build search query
        base_query = select(
            Metric.id,
            Metric.name,
            Metric.type,
            Metric.description,
            Metric.units,
            Metric.component,
            Metric.file_path,
            Metric.line_number,
            MetricOwner.owner.label('owner_name')
        ).outerjoin(
            MetricOwner, Metric.id == MetricOwner.metric_id
        )

        # Build where conditions
        where_conditions = []

        # Type filters
        type_conditions = []
        if search_request.include_uma:
            type_conditions.append(Metric.type == 'histogram')
        if search_request.include_ukm:
            type_conditions.append(Metric.type.in_(['ukm_event', 'ukm_metric']))
        if search_request.include_enums:
            type_conditions.append(Metric.type == 'enum')

        if type_conditions:
            where_conditions.append(or_(*type_conditions))

        # Component filter
        if search_request.component_filter:
            where_conditions.append(Metric.component == search_request.component_filter)

        # Build search conditions
        search_conditions = [
            Metric.name.ilike(f"%{query}%"),
            Metric.description.ilike(f"%{query}%"),
            Metric.component.ilike(f"%{query}%"),
            MetricOwner.owner.ilike(f"%{query}%")
        ]

        if search_conditions:
            where_conditions.append(or_(*search_conditions))

        if where_conditions:
            base_query = base_query.where(and_(*where_conditions))

        # Get total count
        count_query = select(func.count()).select_from(base_query.subquery())
        total_result = await extractor_db.execute(count_query)
        total = total_result.scalar() or 0

        # Apply pagination
        offset = (page - 1) * page_size
        search_query = base_query.offset(offset).limit(page_size)

        result = await extractor_db.execute(search_query)
        metrics = result.fetchall()

        # Process results with scoring and team assignments
        search_results = []
        for metric in metrics:
            # Calculate match scores
            name_score, name_matches = calculate_match_score(query, metric.name)
            desc_score, desc_matches = calculate_match_score(query, metric.description)
            comp_score, comp_matches = calculate_match_score(query, metric.component)
            owner_score, owner_matches = calculate_match_score(query, metric.owner)

            total_score = max(name_score, desc_score, comp_score, owner_score)
            match_fields = list(set(name_matches + desc_matches + comp_matches + owner_matches))

            # Get team assignment
            team_assignment = await get_team_assignment_for_file(
                metric.file_path, business_db
            )

            # Apply team filter if specified
            if search_request.team_filter:
                if not team_assignment or team_assignment["team_id"] != search_request.team_filter:
                    continue

            search_result = SearchResult(
                id=metric.id,
                name=metric.name,
                type=metric.type,
                description=metric.description,
                units=metric.units,
                component=metric.component,
                file_path=metric.file_path,
                line_number=metric.line_number,
                owner=metric.owner_name,
                assigned_team_id=team_assignment["team_id"] if team_assignment else None,
                assigned_team_name=team_assignment["team_name"] if team_assignment else None,
                match_score=total_score,
                match_fields=match_fields
            )
            search_results.append(search_result)

        # Sort by match score (descending)
        search_results.sort(key=lambda x: x.match_score, reverse=True)

        return SearchResponse(
            success=True,
            data=search_results,
            total=total,
            query=query,
            page=page,
            page_size=page_size
        )

    except Exception as e:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Search failed: {str(e)}"
        )


@router.get("/suggestions", response_model=SearchSuggestionsResponse)
async def get_search_suggestions(
    q: str = Query(..., min_length=1),
    limit: int = Query(10, ge=1, le=50),
    current_user: dict = Depends(get_current_user),
    extractor_db: AsyncSession = Depends(get_extractor_db)
):
    """Get search suggestions based on partial query"""
    try:
        suggestions = set()
        query_pattern = f"%{q}%"

        # Get name suggestions
        name_query = select(Metric.name).where(
            Metric.name.ilike(query_pattern)
        ).limit(limit)
        name_result = await extractor_db.execute(name_query)
        suggestions.update([row[0] for row in name_result if row[0]])

        # Get component suggestions
        component_query = select(Metric.component).where(
            and_(
                Metric.component.ilike(query_pattern),
                Metric.component.isnot(None)
            )
        ).distinct().limit(limit)
        component_result = await extractor_db.execute(component_query)
        suggestions.update([row[0] for row in component_result if row[0]])

        # Get owner suggestions
        owner_query = select(MetricOwner.owner).where(
            MetricOwner.owner.ilike(query_pattern)
        ).distinct().limit(limit)
        owner_result = await extractor_db.execute(owner_query)
        suggestions.update([row[0] for row in owner_result if row[0]])

        # Sort and limit suggestions
        sorted_suggestions = sorted(list(suggestions))[:limit]

        return SearchSuggestionsResponse(
            success=True,
            suggestions=sorted_suggestions,
            message="Suggestions retrieved successfully"
        )

    except Exception as e:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to get suggestions: {str(e)}"
        )


@router.get("/components", response_model=List[str])
async def get_components(
    limit: int = Query(100, ge=1, le=1000),
    current_user: dict = Depends(get_current_user),
    extractor_db: AsyncSession = Depends(get_extractor_db)
):
    """Get list of all components for filtering"""
    try:
        query = select(Metric.component).where(
            Metric.component.isnot(None)
        ).distinct().limit(limit)

        result = await extractor_db.execute(query)
        components = [row[0] for row in result if row[0]]

        return sorted(components)

    except Exception as e:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to get components: {str(e)}"
        )


@router.get("/tokens", response_model=List[str])
async def get_tokens(
    limit: int = Query(100, ge=1, le=1000),
    current_user: dict = Depends(get_current_user),
    extractor_db: AsyncSession = Depends(get_extractor_db)
):
    """Get list of all tokens for searching"""
    try:
        query = select(Token.name).limit(limit)

        result = await extractor_db.execute(query)
        tokens = [row[0] for row in result if row[0]]

        return sorted(tokens)

    except Exception as e:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to get tokens: {str(e)}"
        )